"""
Utility functions for GCR Solver Manager.
"""

import hashlib
import os
import time
from pathlib import Path
from typing import Union, Optional
import logging
from datetime import datetime, timezone, timedelta

logger = logging.getLogger(__name__)


def get_project_root() -> Path:
    """
    Get the project root directory.
    
    Returns:
        Path to the project root directory
    """
    current_file = Path(__file__).resolve()
    # Navigate up from src/core/utils.py to project root
    return current_file.parent.parent.parent


def hash_file(file_path: Union[str, Path], algorithm: str = "sha256") -> str:
    """
    Calculate hash of a file.
    
    Args:
        file_path: Path to the file
        algorithm: Hash algorithm to use (md5, sha1, sha256, etc.)
        
    Returns:
        Hex digest of the file hash
        
    Raises:
        FileNotFoundError: If file doesn't exist
        ValueError: If algorithm is not supported
    """
    file_path = Path(file_path)
    if not file_path.exists():
        raise FileNotFoundError(f"File not found: {file_path}")
    
    try:
        hash_obj = hashlib.new(algorithm)
    except ValueError:
        raise ValueError(f"Unsupported hash algorithm: {algorithm}")
    
    with open(file_path, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            hash_obj.update(chunk)
    
    return hash_obj.hexdigest()


def hash_string(content: str, algorithm: str = "sha256") -> str:
    """
    Calculate hash of a string.
    
    Args:
        content: String content to hash
        algorithm: Hash algorithm to use
        
    Returns:
        Hex digest of the string hash
    """
    try:
        hash_obj = hashlib.new(algorithm)
    except ValueError:
        raise ValueError(f"Unsupported hash algorithm: {algorithm}")
    
    hash_obj.update(content.encode('utf-8'))
    return hash_obj.hexdigest()


def format_bytes(bytes_count: int) -> str:
    """
    Format bytes into human-readable string.
    
    Args:
        bytes_count: Number of bytes
        
    Returns:
        Formatted string (e.g., "1.2 GB", "45.3 MB")
    """
    if bytes_count == 0:
        return "0 B"
    
    units = ["B", "KB", "MB", "GB", "TB", "PB"]
    unit_index = 0
    size = float(bytes_count)
    
    while size >= 1024.0 and unit_index < len(units) - 1:
        size /= 1024.0
        unit_index += 1
    
    if unit_index == 0:
        return f"{int(size)} {units[unit_index]}"
    else:
        return f"{size:.1f} {units[unit_index]}"


def format_duration(seconds: float) -> str:
    """
    Format duration in seconds to human-readable string.
    
    Args:
        seconds: Duration in seconds
        
    Returns:
        Formatted string (e.g., "2h 34m 56s", "1m 23s")
    """
    if seconds < 60:
        return f"{seconds:.1f}s"
    
    minutes = int(seconds // 60)
    remaining_seconds = seconds % 60
    
    if minutes < 60:
        return f"{minutes}m {remaining_seconds:.1f}s"
    
    hours = minutes // 60
    remaining_minutes = minutes % 60
    
    if hours < 24:
        return f"{hours}h {remaining_minutes}m {remaining_seconds:.0f}s"
    
    days = hours // 24
    remaining_hours = hours % 24
    
    return f"{days}d {remaining_hours}h {remaining_minutes}m"


def ensure_directory(path: Union[str, Path]) -> Path:
    """
    Ensure a directory exists, creating it if necessary.
    
    Args:
        path: Directory path
        
    Returns:
        Path object for the directory
    """
    path = Path(path)
    path.mkdir(parents=True, exist_ok=True)
    return path


def safe_filename(filename: str) -> str:
    """
    Convert a string to a safe filename.
    
    Args:
        filename: Original filename
        
    Returns:
        Safe filename with invalid characters replaced
    """
    # Replace invalid characters with underscores
    invalid_chars = '<>:"/\\|?*'
    safe_name = filename
    for char in invalid_chars:
        safe_name = safe_name.replace(char, '_')
    
    # Remove leading/trailing periods and spaces
    safe_name = safe_name.strip('. ')
    
    # Limit length
    if len(safe_name) > 255:
        safe_name = safe_name[:255]
    
    return safe_name


def get_timestamp() -> str:
    """
    Get current timestamp in ISO format using local timezone.
    
    Returns:
        ISO formatted timestamp string in local timezone
    """
    return datetime.now().strftime("%Y-%m-%dT%H:%M:%S")


def get_file_timestamp() -> str:
    """
    Get current timestamp suitable for filenames using local timezone.
    
    Returns:
        Timestamp string suitable for filenames (YYYYMMDD_HHMMSS) in local timezone
    """
    return datetime.now().strftime("%Y%m%d_%H%M%S")


class Timer:
    """Context manager for timing operations."""
    
    def __init__(self, description: Optional[str] = None):
        self.description = description
        self.start_time = None
        self.end_time = None
        self.duration = None
    
    def __enter__(self):
        self.start_time = time.time()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.end_time = time.time()
        self.duration = self.end_time - self.start_time
        
        if self.description:
            logger.info(f"{self.description}: {format_duration(self.duration)}")
    
    def elapsed(self) -> float:
        """Get elapsed time since timer started."""
        if self.start_time is None:
            return 0.0
        current_time = time.time()
        return current_time - self.start_time